package com.mq.mqserver.core;

import com.mq.common.exception.MqException;

/*
* 使用这个类实现交换机转发规则
* 同时也借助这个类验证 bindingKey 是否合法
* */
public class Router {
    //bangdingKey 的构造规则：
    //1.数字，字母，下划线
    //2.使用. 分割若干部分
    //3.允许在 * 和 # 作为通配符，但是只能作为独立的段
    public boolean checkBindingKey(String bindingKey){
        //空字符串也是合法情况，比如使用 direct / fanout 交换机， bindingKey没有用
        if(bindingKey.length() == 0)
            return true;

        //检查是否存在不合法字符
        for(int i =0;i < bindingKey.length();i++){
            char ch = bindingKey.charAt(i);
            //判定还字符是否大写字母
            if(ch >= 'A' && ch <= 'Z'){
                continue;
            }
            if(ch >= 'a' && ch <= 'z'){
                continue;
            }
            //判定是不是阿拉伯数字
            if(ch >= '0' && ch <= '9'){
                continue;
            }
            //判定是否是_或者.
            if(ch == '_' || ch == '.' || ch == '*' || ch == '#'){
                continue;
            }
            //其他情况会出错
            return false;
        }
        //检查 * # 是否是独立的部分
        //aaa.*.bbb合法  aaa.*bbb 不合法

        //.在正则表达式中是一个特殊的符号，此处是用于文本匹配，因此需要转义\. ,
        // 在java中"\."是特殊字符，需要转义
        String[] words = bindingKey.split("\\.");
        for(String word:words){
            //检查word长度 > 1 并且包含了 * 或者 # ，就是非法的格式，因为包含 * 或者 #的部分长度是1，不能大于1
            if(word.length() > 1 && (word.contains("*") || word.contains("#"))){
                return false;
            }
        }
        //自己约定以下非法，因为实现繁琐，提升功能性也不大
        // 1.aaa.#.#.bbb 非法
        // 2.aaa.#.*.bbb 非法
        // 3.aaa.*.#.bbb 非法
        // 4.aaa.*.*.bbb 非法
        for(int i = 0; i < words.length - 1;i++){
            //连续两个 ##
            if(words[i].equals(("#")) && words[i + 1].equals("#")){
                return false;
            }
            //连续两个 **
            if(words[i].equals(("*")) && words[i + 1].equals("*")){
                return false;
            }
            //# 连着 *
            if(words.equals("#") && words[i + 1].equals("*")){
                return false;
            }
            //* 连着 #
            if(words[i].equals("*") && words[i + 1].equals("#")){
                return false;
            }
        }
        return true;
    }
    //routingKey 构造规则；
    //1.数字，字母，下划线
    //2.使用：分割成若干部分
    public boolean checkRoutingKey(String routingKey){
        if(routingKey.length() == 0){
            //空字符串，合法的情况下，比如在使用 fanout 交换机的时候，routingKey 用不上可以设置为""
            return true;
        }
        for(int i =0;i < routingKey.length();i++){
            char ch = routingKey.charAt(i);
            //判定还字符是否大写字母
            if(ch >= 'A' && ch <= 'Z'){
                continue;
            }
            if(ch >= 'a' && ch <= 'z'){
                continue;
            }
            //判定是不是阿拉伯数字
            if(ch >= '0' && ch <= '9'){
                continue;
            }
            //判定是否是_或者.
            if(ch == '_' || ch == '.'){
                continue;
            }
            //其他情况会出错
            return false;
        }
        return true;

    }
    //判定消息能不能转发给该绑定对应的队列
    public boolean route(ExchangeType exchangeType,Binding binding,Message message) throws MqException {
        //根据不同的 rchangeType 使用不同的判定转发规则
        if(exchangeType == ExchangeType.FANOUT){
            //FANOUT类型交换机，该交换机上的绑定的所有队列都需要转发
            return true;
        }else if(exchangeType == ExchangeType.TOPIC){
            //TOPIC交换机，需要判定
            return routeTopic(binding,message);
        }else {
            throw new MqException("[Router] 交换机类型非法 exchangeType = " + exchangeType);
        }
    }
    //实现bindingKey和routingKey 匹配
    private boolean routeTopic(Binding binding, Message message) {
        String[] bindingTokens = binding.getBindingKey().split("\\.");
        String[] routingTokens = message.getRountingKey().split("\\.");

        // 处理空 bindingKey 或 routingKey 的情况
        if (bindingTokens.length == 0 && routingTokens.length == 0) {
            return true;
        }
        if (bindingTokens.length == 0 || routingTokens.length == 0) {
            return false;
        }

        //引入两个下标
        int bindingIndex = 0;
        int routingIndex = 0;
        //每次循环不一定加1.使用while
        while(bindingIndex <bindingTokens.length && routingIndex < routingTokens.length){
            if(bindingTokens[bindingIndex].equals("*")){
                //[情况二]到了*就直接进入下一步
                bindingIndex++;
                routingIndex++;

            }else if(bindingTokens[bindingIndex].equals("#")){
                bindingIndex++;
                if(bindingIndex == bindingTokens.length){
                    //[情况三]#后没有东西，匹配成功
                    return true;
                }
                //[情况四]#后面有东西，继续匹配
                //findNextMatch 这个方法用来查找该部分在 routingKey 的位置，返回该下标，没找到，就返回 -1
                routingIndex = findNextMatch(routingTokens,routingIndex,bindingTokens[bindingIndex]);
                if(routingIndex == -1){
                    //没找到匹配的结果，匹配失败
                    return false;
                }
                bindingIndex++;
                routingIndex++;
            }else{
                //[情况一]如果遇到普通字符串，要求两边的内容是一样的
                if(!bindingTokens[bindingIndex].equals(routingTokens[routingIndex])){
                    return false;
                }
                bindingIndex++;
                routingIndex++;
            }
        }
        //[情况五]判定双方是否同时到达末尾
        //比如 aaa.bbb.ccc 和 aaa.bbb 要是匹配失败的
       /* if(bindingIndex == bindingTokens.length && routingIndex == routingTokens.length){
            return true;
        }*/
        // 确保两边同时到达末尾，除非 bindingKey 以 # 结尾
        if (bindingIndex < bindingTokens.length ) {
            return bindingIndex > 0 && bindingTokens[bindingIndex - 1].equals("#");  // bindingKey 有未处理的非 # 段
        }
        if (routingIndex < routingTokens.length) {
            return false; // routingKey 有未处理的段
        }
        return true;
    }

    private int findNextMatch(String[] routingTokens, int routingIndex, String bindingToken) {
        for(int i = routingIndex ; i < routingTokens.length;i++){
            if(routingTokens[i].equals(bindingToken)){
                return i;
            }
        }
        return -1;
    }

}
